<?php
/**
 * PEAR_Sniffs_Functions_FunctionCallSignatureSniff.
 *
 * PHP version 5
 *
 * @category  PHP
 * @package   PHP_CodeSniffer
 * @author    Greg Sherwood <gsherwood@squiz.net>
 * @author    Marc McIntyre <mmcintyre@squiz.net>
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
 * @version   CVS: $Id: FunctionCallSignatureSniff.php 284575 2009-07-22 02:58:19Z squiz $
 * @link      http://pear.php.net/package/PHP_CodeSniffer
 */

/**
 * PEAR_Sniffs_Functions_FunctionCallSignatureSniff.
 *
 * @category  PHP
 * @package   PHP_CodeSniffer
 * @author    Greg Sherwood <gsherwood@squiz.net>
 * @author    Marc McIntyre <mmcintyre@squiz.net>
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
 * @version   Release: 1.2.1
 * @link      http://pear.php.net/package/PHP_CodeSniffer
 */
class Fajr_Sniffs_Functions_FunctionCallSignatureSniff implements PHP_CodeSniffer_Sniff
{


  /**
   * Returns an array of tokens this test wants to listen for.
   *
   * @return array
   */
  public function register()
  {
    return array(T_STRING);

  }//end register()


  /**
   * Processes this test, when one of its tokens is encountered.
   *
   * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
   * @param int                  $stackPtr  The position of the current token
   *                                        in the stack passed in $tokens.
   *
   * @return void
   */
  public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
  {
    $tokens = $phpcsFile->getTokens();

    // Find the next non-empty token.
    $openBracket = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);

    if ($tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) {
      // Not a function call.
      return;
    }

    if (isset($tokens[$openBracket]['parenthesis_closer']) === false) {
      // Not a function call.
      return;
    }

    // Find the previous non-empty token.
    $search   = PHP_CodeSniffer_Tokens::$emptyTokens;
    $search[] = T_BITWISE_AND;
    $previous = $phpcsFile->findPrevious($search, ($stackPtr - 1), null, true);
    if ($tokens[$previous]['code'] === T_FUNCTION) {
      // It's a function definition, not a function call.
      return;
    }

    if ($tokens[$previous]['code'] === T_NEW) {
      // We are creating an object, not calling a function.
      return;
    }

    $closeBracket = $tokens[$openBracket]['parenthesis_closer'];

    if (($stackPtr + 1) !== $openBracket) {
      // Checking this: $value = my_function[*](...).
      $error = 'Space before opening parenthesis of function call prohibited';
      $phpcsFile->addError($error, $stackPtr);
    }

    $next = $phpcsFile->findNext(T_WHITESPACE, ($closeBracket + 1), null, true);
    if ($tokens[$next]['code'] === T_SEMICOLON) {
      if (in_array($tokens[($closeBracket + 1)]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === true) {
        $error = 'Space after closing parenthesis of function call prohibited';
        $phpcsFile->addError($error, $closeBracket);
      }
    }

    // Check if this is a single line or multi-line function call.
    if ($tokens[$openBracket]['line'] === $tokens[$closeBracket]['line']) {
      $this->processSingleLineCall($phpcsFile, $stackPtr, $openBracket, $tokens);
    } else {
      $this->processMultiLineCall($phpcsFile, $stackPtr, $openBracket, $tokens);
    }

  }//end process()


  /**
   * Processes single-line calls.
   *
   * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
   * @param int                  $stackPtr    The position of the current token
   *                                          in the stack passed in $tokens.
   * @param int                  $openBracket The position of the openning bracket
   *                                          in the stack passed in $tokens.
   * @param array                $tokens      The stack of tokens that make up
   *                                          the file.
   *
   * @return void
   */
  public function processSingleLineCall(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $openBracket, $tokens)
  {
    if ($tokens[($openBracket + 1)]['code'] === T_WHITESPACE) {
      // Checking this: $value = my_function([*]...).
      $error = 'Space after opening parenthesis of function call prohibited';
      $phpcsFile->addError($error, $stackPtr);
    }

    $closer = $tokens[$openBracket]['parenthesis_closer'];

    if ($tokens[($closer - 1)]['code'] === T_WHITESPACE) {
      // Checking this: $value = my_function(...[*]).
      $between = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), null, true);

      // Only throw an error if there is some content between the parenthesis.
      // i.e., Checking for this: $value = my_function().
      // If there is no content, then we would have thrown an error in the
      // previous IF statement because it would look like this:
      // $value = my_function( ).
      if ($between !== $closer) {
        $error = 'Space before closing parenthesis of function call prohibited';
        $phpcsFile->addError($error, $closer);
      }
    }

  }//end processSingleLineCall()


  /**
   * Processes multi-line calls.
   *
   * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
   * @param int                  $stackPtr    The position of the current token
   *                                          in the stack passed in $tokens.
   * @param int                  $openBracket The position of the openning bracket
   *                                          in the stack passed in $tokens.
   * @param array                $tokens      The stack of tokens that make up
   *                                          the file.
   *
   * @return void
   */
  public function processMultiLineCall(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $openBracket, $tokens)
  {
    // We need to work out how far indented the function
    // call itself is, so we can work out how far to
    // indent the arguments.
    $functionIndent = 0;
    for ($i = ($stackPtr - 1); $i >= 0; $i--) {
      if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) {
        $i++;
        break;
      }
    }

    if ($tokens[$i]['code'] === T_WHITESPACE) {
      $functionIndent = strlen($tokens[$i]['content']);
    }

    // Each line between the parenthesis should be indented 4 spaces.
    $closeBracket = $tokens[$openBracket]['parenthesis_closer'];
    $lastLine     = $tokens[$openBracket]['line'];
    $secondLineIndent = -1;

    for ($i = ($openBracket + 1); $i < $closeBracket; $i++) {
      // Skip nested function calls.
      if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
        $i        = $tokens[$i]['parenthesis_closer'];
        $lastLine = $tokens[$i]['line'];
        continue;
      }

      if ($tokens[$i]['line'] !== $lastLine) {
        $lastLine = $tokens[$i]['line'];

        // We changed lines, so this should be a whitespace indent token.
        if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$heredocTokens) === true) {
          // Ignore heredoc indentation.
          continue;
        }

        if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$stringTokens) === true) {
          if ($tokens[$i]['code'] === $tokens[($i - 1)]['code']) {
            // Ignore multi-line string indentation.
            continue;
          }
        }


        /*
           if ($tokens[$i]['line'] === $tokens[$closeBracket]['line']) {
        // Closing brace needs to be indented to the same level
        // as the function call.
        $expectedIndent = $functionIndent;
        } else {
        $expectedIndent = ($functionIndent + 4);
        }
         */

        if ($tokens[$i]['code'] !== T_WHITESPACE) {
          $foundIndent = 0;
        } else {
          $foundIndent = strlen($tokens[$i]['content']);
        }

        if ($secondLineIndent == -1) {
          $secondLineIndent = $foundIndent;
        }
        $expectedIndent = $secondLineIndent;

        if ($expectedIndent !== $foundIndent) {
          $error = "Multi-line function call not indented correctly; expected $expectedIndent spaces but found $foundIndent";
          $phpcsFile->addError($error, $i);
        }
        if ($foundIndent < $functionIndent + 4) {
          $error = "Multi-line function call not indented by at least 4 spaces; expected "
            . ($functionIndent+4) . " spaces but found $foundIndent";
          $phpcsFile->addError($error, $i);
        }
      }//end if
    }//end for

    /*
       if ($tokens[($openBracket + 1)]['content'] !== $phpcsFile->eolChar) {
       $error = 'Opening parenthesis of a multi-line function call must be the last content on the line';
       $phpcsFile->addError($error, $stackPtr);
       }

       $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($closeBracket - 1), null, true);
       if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) {
       $error = 'Closing parenthesis of a multi-line function call must be on a line by itself';
       $phpcsFile->addError($error, $closeBracket);
       }
     */

  }//end processMultiLineCall()


}//end class
?>
